package com.nageoffer.shortlink.project.service.Impl;

import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.nageoffer.shortlink.project.common.constant.RedisKeyConstant;
import com.nageoffer.shortlink.project.common.convention.exception.ClientException;
import com.nageoffer.shortlink.project.common.convention.exception.ServiceException;
import com.nageoffer.shortlink.project.mapper.ShortLinkGotoMapper;
import com.nageoffer.shortlink.project.mapper.ShortLinkMapper;
import com.nageoffer.shortlink.project.pojo.dto.Req.ShortLinkCreateReqDTO;
import com.nageoffer.shortlink.project.pojo.dto.Req.ShortLinkPageReqDTO;
import com.nageoffer.shortlink.project.pojo.dto.Req.ShortLinkUpdateReqDTO;
import com.nageoffer.shortlink.project.pojo.dto.Resp.ShortLinkCreateRespDTO;
import com.nageoffer.shortlink.project.pojo.dto.Resp.ShortLinkGroupCountRespDTO;
import com.nageoffer.shortlink.project.pojo.dto.Resp.ShortLinkPageRespDTO;
import com.nageoffer.shortlink.project.pojo.entity.ShortLink;
import com.nageoffer.shortlink.project.pojo.entity.ShortLinkGoto;
import com.nageoffer.shortlink.project.service.ShortLinkService;
import com.nageoffer.shortlink.project.toolkit.HashUtil;
import com.nageoffer.shortlink.project.toolkit.LinkUtil;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Service
@RequiredArgsConstructor
@Slf4j
public class ShortLinkServiceImpl extends ServiceImpl<ShortLinkMapper, ShortLink> implements ShortLinkService {
    private final ShortLinkMapper shortLinkMapper;
    private final RBloomFilter<String> shortUrlCreateCachePenetrationBloomFilter;
    private final ShortLinkGotoMapper shortLinkGotoMapper;
    private final StringRedisTemplate stringRedisTemplate;
    private final RedissonClient redissonClient;

    @Override
    @Transactional
    public ShortLinkCreateRespDTO createShortLink(ShortLinkCreateReqDTO requestParam) {

        String shortLinkSuffix = this.getShortLinkSuffix(requestParam);

        ShortLink shortLink = BeanUtil.toBean(requestParam, ShortLink.class);
        String fullShortUrl = shortLink.getDomain()+"/"+shortLinkSuffix;
        shortLink.setShortUri(shortLinkSuffix);
        shortLink.setFullShortUrl(fullShortUrl);
        shortLink.setEnableStatus(0);
        log.info("shortlink:{}",shortLink);
        ShortLinkGoto shortLinkGoto = ShortLinkGoto.builder()
                .fullShortUrl(fullShortUrl)
                .gid(shortLink.getGid())
                .build();
        //TODO 在这里相同的原始链接，可以生成不同的短链接并且没有判断
        try{
            shortLinkMapper.insert(shortLink);
            shortLinkGotoMapper.insert(shortLinkGoto);
        }catch (DuplicateKeyException e){
            //TODO 误判的短链接处理没有完成
            LambdaQueryWrapper<ShortLink> wrapper = Wrappers.lambdaQuery(ShortLink.class)
                    .eq(ShortLink::getFullShortUrl, fullShortUrl)
                    .eq(ShortLink::getOriginUrl,shortLink.getOriginUrl())
                    .eq(ShortLink::getDelFlag,0);
            ShortLink shortLink1 = shortLinkMapper.selectOne(wrapper);
            if(shortLink1 != null){
                throw new ServiceException("短链接生成重复！");
            }

        }
        //TODO 缓存预热
        stringRedisTemplate.opsForValue().set(
                String.format(RedisKeyConstant.ORIGINAL_URL_KEY_PREFIX,fullShortUrl),
                shortLink.getOriginUrl(),
                LinkUtil.getLinkCacheValidDate(shortLink.getValidDate())
                , TimeUnit.MILLISECONDS
                );
        shortUrlCreateCachePenetrationBloomFilter.add(fullShortUrl);

        return ShortLinkCreateRespDTO.builder()
                .gid(shortLink.getGid())
                .fullShortUrl(shortLink.getFullShortUrl())
                .originUrl(shortLink.getOriginUrl())
                .build();
    }

    @Override
    public IPage<ShortLinkPageRespDTO> pageShortLink(ShortLinkPageReqDTO requestParam) {
        LambdaQueryWrapper<ShortLink> wrapper = Wrappers.lambdaQuery(ShortLink.class)
                .eq(ShortLink::getGid, requestParam.getGid())
                .eq(ShortLink::getDelFlag, 0)
                .eq(ShortLink::getEnableStatus, 0);
        IPage<ShortLink> pageParam = new Page<>(requestParam.getPageNum(),requestParam.getPageSize());
        IPage<ShortLink> page = shortLinkMapper.selectPage(pageParam, wrapper);
        return page.convert(shortLink -> BeanUtil.toBean(shortLink,ShortLinkPageRespDTO.class));
    }

    @Override
    public List<ShortLinkGroupCountRespDTO> listGroupShortLinkCount(List<String> gidList) {
        //TODO 利用MyBatis写
        return shortLinkMapper.listGroupShortLinkCount(gidList);
    }

    /**
     * TODO 此时的功能不可以修改分组，并且把他先删除后再插入
     * @param requestParam
     */
    @Override
    public void updateShortLink(ShortLinkUpdateReqDTO requestParam) {
        //先查短链接存在情况
        LambdaQueryWrapper<ShortLink> wrapper = Wrappers.lambdaQuery(ShortLink.class)
                .eq(ShortLink::getFullShortUrl, requestParam.getFullShortUrl())
                .eq(ShortLink::getGid,requestParam.getGid())
                .eq(ShortLink::getDelFlag, 0)
                .eq(ShortLink::getEnableStatus, 0);
        ShortLink shortLink = shortLinkMapper.selectOne(wrapper);
        if(shortLink == null){
            throw new ClientException("此条短链接不存在");
        }
        ShortLink newShortLink = ShortLink.builder()
                .id(shortLink.getId())
                .domain(shortLink.getDomain())
                .shortUri(shortLink.getShortUri())
                .fullShortUrl(shortLink.getFullShortUrl())
                .clickNum(shortLink.getClickNum())
                .favicon(shortLink.getFavicon())
                .createdType(shortLink.getCreatedType())
                .gid(requestParam.getGid())
                .originUrl(requestParam.getOriginUrl())
                .describe(requestParam.getDescribe())
                .validDate(requestParam.getValidDate())
                .validDateType(requestParam.getValidDateType())
                .build();
        //修改的时候判断是否修改了gid,相等则直接修改
        //不相等则先删再改
        shortLinkMapper.delete(wrapper);
        shortLinkMapper.insert(newShortLink);

    }

    //TODO 增添预防缓存击穿功能
    //TODO 增添预防缓存穿透功能
    @Override
    public void restoreUrl(String shortUri, ServletRequest request, ServletResponse response) throws IOException {
        //获取到完整路径
        String scheme = request.getScheme();
        String serverName = request.getServerName();
        String fullShortUrl =  scheme + "://"+ serverName + (serverName.endsWith("/") ? "" : "/") + shortUri;
        String originalUrlKey = String.format(RedisKeyConstant.ORIGINAL_URL_KEY_PREFIX,fullShortUrl);
        String originalUrl = stringRedisTemplate.opsForValue().get(originalUrlKey);
        //可以从redis中查找到
        if(Strings.isNotBlank(originalUrl)){
            ((HttpServletResponse)response).sendRedirect(originalUrl);
            return;
        }
        //未能从redis中查找到
        //先增加缓存穿透功能
        boolean contains = shortUrlCreateCachePenetrationBloomFilter.contains(fullShortUrl);
        if(!contains){
            return;
        }
        String isNulloriginalUrlKey = stringRedisTemplate.opsForValue().get(String.format(RedisKeyConstant.ORIGINAL_URL_IS_NOT_KEY_PREFIX,fullShortUrl));
        if(Strings.isNotBlank(isNulloriginalUrlKey)){
            return;
        }
        //需要加锁，防止缓存穿透
        RLock lock = redissonClient.getLock(String.format(RedisKeyConstant.ORIGINAL_URL_LOCK_KEY_PREFIX,fullShortUrl));
        lock.lock();
        try{
            originalUrl = stringRedisTemplate.opsForValue().get(originalUrlKey);
            if(Strings.isNotBlank(originalUrl)){
                ((HttpServletResponse)response).sendRedirect(originalUrl);
                return;
            }
            //根据完整路径通过link_goto表来查gid
            LambdaQueryWrapper<ShortLinkGoto> w = Wrappers.lambdaQuery(ShortLinkGoto.class)
                    .eq(ShortLinkGoto::getFullShortUrl, fullShortUrl);
            ShortLinkGoto shortLinkGoto = shortLinkGotoMapper.selectOne(w);
            if(shortLinkGoto == null){
                stringRedisTemplate.opsForValue().set(String.format(RedisKeyConstant.ORIGINAL_URL_IS_NOT_KEY_PREFIX,fullShortUrl),"-", Duration.ofSeconds(30));
                throw new ServiceException("无相对应的链接，服务错误！");
            }
            //根据gid查找原始路径
            LambdaQueryWrapper<ShortLink> wrapper = Wrappers.lambdaQuery(ShortLink.class)
                    .eq(ShortLink::getGid, shortLinkGoto.getGid())
                    .eq(ShortLink::getFullShortUrl, shortLinkGoto.getFullShortUrl())
                    .eq(ShortLink::getDelFlag,0)
                    .eq(ShortLink::getEnableStatus,0);
            ShortLink shortLink = shortLinkMapper.selectOne(wrapper);

            if(shortLink == null){
                throw new ServiceException("短链接为空，服务出现错误！");
            }
            stringRedisTemplate.opsForValue().set(originalUrlKey,shortLink.getOriginUrl());
            ((HttpServletResponse)response).sendRedirect(shortLink.getOriginUrl());
        }finally {
            lock.unlock();
        }

    }

    private String getShortLinkSuffix(ShortLinkCreateReqDTO requestParam){
        String originUrl = requestParam.getOriginUrl();
        int customGenerateCount = 0;
        String shortUri;
        while(true){
            if(customGenerateCount > 10){
                throw new ServiceException("短链接频繁生成，请稍后再试");
            }
            originUrl += System.currentTimeMillis();
            shortUri = HashUtil.hashToBase62(originUrl);
            String  fullShortUrl = requestParam.getDomain()+"/"+shortUri;
            if(!shortUrlCreateCachePenetrationBloomFilter.contains(fullShortUrl)){
                break;
            }
            customGenerateCount++;
        }
        return shortUri;
    }
}
